/*
 * libustream: User-space stackable streams
 * @(#) $Id: stream.c 2 2009-02-20 09:52:11Z mo@ilaven.net $
 */

/*
 * Copyright (c) 2002, 2008 Mo McRoberts.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The names of the author(s) of this software may not be used to endorse
 *    or promote products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * AUTHORS OF THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "p_libustream.h"

static com_result_t __stdcall UStream_QueryInterface(IUStream *intf, com_riid_t iid, void **out);
static uint32_t __stdcall UStream_AddRef(IUStream *intf);
static uint32_t __stdcall UStream_Release(IUStream *intf);
static com_result_t __stdcall UStream_GetDescriptor(IUStream *intf, int *fd);
static com_result_t __stdcall UStream_GetIRI(IUStream *intf, const iri_t **iri);
static com_result_t __stdcall UStream_GetOpenMode(IUStream *intf, int *oflag, mode_t *mode);
static com_result_t __stdcall UStream_Accept(IUStream *intf, IUStream **child);
static com_result_t __stdcall UStream_Read(IUStream *intf, void *buf, size_t *buflen);
static com_result_t __stdcall UStream_Write(IUStream *intf, const void *buf, size_t *buflen);

DECLARE_CLASS(UStream)
{
	DECLARE_IMPLEMENTS(UStream, IUStream);
	uint32_t refcount;
	pthread_key_t key;
	iri_t *iri;
	int fd;
	int oflag;
	mode_t mode;
	size_t nmodules;
	IUStreamNode **modules;
};

DEFINE_CLASS_INTERFACE(UStream, IUStream)
{
	UStream_QueryInterface,
	UStream_AddRef,
	UStream_Release,
	UStream_GetDescriptor,
	UStream_GetIRI,
	UStream_GetOpenMode,
	UStream_Accept,
	UStream_Read,
	UStream_Write,
	NULL
};

IUStream *
us__stream_create(const iri_t *iri, int oflag, mode_t mode)
{
	UStream *us;
	
	if(NULL == (us = (UStream *) calloc(1, sizeof(UStream))))
	{
		return NULL;
	}
	us->iri = iri_dup(iri);
	us->fd = -1;
	us->oflag = oflag;
	us->mode = mode;
	pthread_key_create(&(us->key), NULL);
	INITIALISE_ADDREF_INTERFACE_POINTER(UStream, us, IUStream);
	return GET_INTERFACE_POINTER(us, IUStream);
}

int
us__stream_push(IUStream *stream, IUStreamNode *node, int fd)
{
	IUStreamNode **p;
	DECLARE_SELF(UStream, IUStream, stream);
	
	if(NULL == (p = (IUStreamNode **) realloc(self->modules, sizeof(IUStreamNode *) * (self->nmodules + 1))))
	{
		return -1;
	}
	IUStreamNode_AddRef(node);
	if(0 == self->nmodules)
	{
/*		fprintf(stderr, "Setting descriptor of stream to %d\n", fd); */
		self->fd = fd;
	}
	p[self->nmodules] = node;
	self->nmodules++;
	self->modules = p;
	return 0;
}

static inline IUStreamNode *
us__stream_nextnode(IUStream *stream)
{
	size_t idx;
	IUStreamNode *node;
	
	DECLARE_SELF(UStream, IUStream, stream);
	if(0 == self->nmodules)
	{
		return NULL;
	}
	idx = (size_t) pthread_getspecific(self->key);
	if(0xFFFFFFF == idx)
	{
		/* Attempt to go past the head node */
/*		fprintf(stderr, "Attempt to go past the head node\n"); */
		return NULL;
	}
	if(0 == idx)
	{
/*		fprintf(stderr, "Starting at tail node\n"); */
		/* Start of a new chain */
		idx = self->nmodules;
	}
	idx--;
	node = self->modules[idx];
	if(0 == idx)
	{
		/* Mark the fact we've reached the head */
/*		fprintf(stderr, "Reached the head node, setting index to FFFFFFFF\n"); */
		idx = 0xFFFFFFFF;
	}
	pthread_setspecific(self->key, (void *) idx);
	return node;
}

static inline void
us__stream_unwind(IUStream *stream)
{
	size_t idx;
	
	DECLARE_SELF(UStream, IUStream, stream);
	idx = (size_t) pthread_getspecific(self->key);
	if(0xFFFFFFFF == idx)
	{
		idx = 1;
	}
	else if(0 == idx)
	{
		/* Mismatch between _nextnode and _unwind */
	}
	else
	{
		idx++;
	}
	if(idx >= self->nmodules)
	{
		/* We've reached the end of the chain */
		pthread_setspecific(self->key, NULL);
	}
	else
	{
		pthread_setspecific(self->key, (void *) idx);
	}
}

static com_result_t __stdcall
UStream_QueryInterface(IUStream *intf, com_riid_t iid, void **out)
{
	if(com_guid_equal(iid, &IID_IUnknown) || com_guid_equal(iid, &IID_IUStream))
	{
		UStream_AddRef(intf);
		*out = (void *) intf;
		return COM_S_OK;
	}
	return COM_E_NOINTERFACE;
}

static uint32_t __stdcall
UStream_AddRef(IUStream *intf)
{
	DECLARE_SELF(UStream, IUStream, intf);
	
	self->refcount++;
	return self->refcount;
}

static uint32_t __stdcall
UStream_Release(IUStream *intf)
{
	size_t c;
	
	DECLARE_SELF(UStream, IUStream, intf);
	
	self->refcount--;
	if(0 == self->refcount)
	{
		/* Close the stream */
/*		fprintf(stderr, "Closing stream\n"); */
		if(self->fd != -1)
		{
			us__fd_disassociate(self->fd, intf);
		}
		for(c = 0; c < self->nmodules; c++)
		{
			IUStreamNode_Release(self->modules[c]);
		}
		free(self->modules);
		pthread_key_delete(self->key);
		free(self);
		return 0;
	}
	return self->refcount;
}

static com_result_t __stdcall
UStream_GetDescriptor(IUStream *intf, int *fd)
{
	DECLARE_SELF(UStream, IUStream, intf);
	
	if(NULL != fd)
	{
		*fd = self->fd;
	}
	return COM_S_OK;
}

static com_result_t __stdcall
UStream_GetIRI(IUStream *intf, const iri_t **iri)
{
	DECLARE_SELF(UStream, IUStream, intf);
	
	if(NULL != iri)
	{
		*iri = self->iri;
	}
	return COM_S_OK;
}

static com_result_t __stdcall
UStream_GetOpenMode(IUStream *intf, int *oflag, mode_t *mode)
{
	DECLARE_SELF(UStream, IUStream, intf);
	
	if(NULL != oflag)
	{
		*oflag = self->oflag;
	}
	if(NULL != mode)
	{
		*mode = self->mode;
	}
	return COM_S_OK;
}

static com_result_t __stdcall
UStream_Write(IUStream *intf, const void *buf, size_t *buflen)
{
	IUStreamNode *node;
	com_result_t r;
	
	if(NULL == (node = us__stream_nextnode(intf)))
	{
/*		fprintf(stderr, "us__stream_nextnode() failed\n"); */
		return COM_E_NOT_SUPPORTED;
	}
	r = IUStreamNode_Write(node, intf, buf, buflen);
	us__stream_unwind(intf);
	return r;
}

static com_result_t __stdcall
UStream_Read(IUStream *intf, void *buf, size_t *buflen)
{
	IUStreamNode *node;
	com_result_t r;
	
	if(NULL == (node = us__stream_nextnode(intf)))
	{
		return COM_E_NOT_SUPPORTED;
	}
	r = IUStreamNode_Read(node, intf, buf, buflen);
	us__stream_unwind(intf);
	return r;
}

static com_result_t __stdcall
UStream_Accept(IUStream *intf, IUStream **out)
{
	UStream *us;
	IUStream *ptr;
	IUStreamNode *node, *childNode;
	int fd;
	com_result_t r;
	size_t c;
	
	DECLARE_SELF(UStream, IUStream, intf);
	
	if(NULL == (node = us__stream_nextnode(intf)))
	{
		return COM_E_NOT_SUPPORTED;
	}
	if(NULL == (us = (UStream *) calloc(1, sizeof(UStream))))
	{
		return errno;
	}
	us->fd = -1;
	us->iri = NULL;
	us->oflag = O_RDWR;
	r = IUStreamNode_Accept(node, intf, &childNode, &fd);
	us__stream_unwind(intf);
	if(COM_S_OK != r)
	{
		free(us);
		return r;
	}
	pthread_key_create(&(us->key), NULL);
	INITIALISE_ADDREF_INTERFACE_POINTER(UStream, us, IUStream);
	ptr = GET_INTERFACE_POINTER(us, IUStream);
	us__stream_push(ptr, childNode, fd);
	/* Walk the list of nodes attached to the parent, and call GetChildNode()
	 * on each, pushing the result onto the child stream.
	 */
	for(c = 1; c < self->nmodules; c++)
	{
		/* Ensure writes to the child get sent to the correct node */
		pthread_setspecific(us->key, (void *) c);
		if(COM_S_OK != (r = IUStreamNode_GetChildNode(self->modules[c], intf, ptr, &childNode)))
		{
			IUStream_Release(ptr);
			return r;
		}
		us__stream_push(ptr, childNode, fd);
	}
	pthread_setspecific(us->key, (void *) NULL);
	*out = ptr;
	return COM_S_OK;
}
